/*********************************************************************************************************
 *  ------------------------------------------------------------------------------------------------------
 *  file description
 *  ------------------------------------------------------------------------------------------------------
 *         \file  unitt.c
 *         \unit  unitt
 *        \brief  This is a simple unit test module for C language
 *       \author  Lamdonn
 *      \version  v1.0.0
 *      \license  GPL-2.0
 *    \copyright  Copyright (C) 2023 Lamdonn.
 ********************************************************************************************************/
#include "unitt.h"

/**
 * \brief Assert that a condition is true.
 * 
 * This function checks if the given condition is true. If it is false,
 * it outputs an assertion failure message and returns a failure code.
 * 
 * \param[in] condition: The condition to evaluate.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_true(int condition, const char* message) 
{
    if (!condition) 
    {
        fprintf(stderr, "Assertion Failed: %s\n", message);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Assert that a condition is false.
 * 
 * This function checks if the given condition is false. If it is true,
 * it outputs an assertion failure message and returns a failure code.
 * 
 * \param[in] condition: The condition to evaluate.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_false(int condition, const char* message) 
{
    if (condition) 
    {
        fprintf(stderr, "Assertion Failed: %s\n", message);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Assert that two integers are equal.
 * 
 * This function checks whether the expected and actual values are equal.
 * If they are not equal, it outputs an assertion failure message with
 * both values and returns a failure code.
 * 
 * \param[in] expected: The expected value.
 * \param[in] actual: The actual value.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_equal(int expected, int actual, const char* message) 
{
    if (expected != actual) 
    {
        fprintf(stderr, "Assertion Failed: %s (Expected: %d, Actual: %d)\n", message, expected, actual);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Assert that two integers are not equal.
 * 
 * This function checks whether the expected and actual values are not equal.
 * If they are equal, it outputs an assertion failure message and returns a failure code.
 * 
 * \param[in] expected: The expected value.
 * \param[in] actual: The actual value.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_nequal(int expected, int actual, const char* message) 
{
    if (expected == actual) 
    {
        fprintf(stderr, "Assertion Failed: %s (Expected not equal to Actual)\n", message);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Assert that a pointer is NULL.
 * 
 * This function checks whether the given pointer is NULL. If it is not NULL,
 * it outputs an assertion failure message with the pointer's value and returns a failure code.
 * 
 * \param[in] pointer: The pointer to evaluate.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_null(void* pointer, const char* message) 
{
    if (pointer != NULL) 
    {
        fprintf(stderr, "Assertion Failed: %s (Expected NULL, Actual: %p)\n", message, pointer);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Assert that a pointer is not NULL.
 * 
 * This function checks whether the given pointer is not NULL. If it is NULL,
 * it outputs an assertion failure message and returns a failure code.
 * 
 * \param[in] pointer: The pointer to evaluate.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_nnull(void* pointer, const char* message) 
{
    if (pointer == NULL) 
    {
        fprintf(stderr, "Assertion Failed: %s (Expected non-NULL, Actual: NULL)\n", message);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Assert that two floats are approximately equal.
 * 
 * This function checks whether the expected and actual float values are approximately equal
 * within a given epsilon range. If they are not, it outputs an assertion failure message
 * with both values and returns a failure code.
 * 
 * \param[in] expected: The expected float value.
 * \param[in] actual: The actual float value.
 * \param[in] epsilon: The acceptable difference between the two values.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_float(float expected, float actual, float epsilon, const char* message) 
{
    if (fabs(expected - actual) > epsilon) 
    {
        fprintf(stderr, "Assertion Failed: %s (Expected: %.6f, Actual: %.6f)\n", message, expected, actual);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Assert that two strings are equal.
 * 
 * This function checks whether the expected and actual strings are equal.
 * If they are not equal, it outputs an assertion failure message with both strings
 * and returns a failure code.
 * 
 * \param[in] expected: The expected string.
 * \param[in] actual: The actual string.
 * \param[in] message: The message to display if the assertion fails.
 * \return Returns UNITT_E_OK if the assertion passes, otherwise UNITT_E_FAIL.
 */
int unitt_det_string(const char* expected, const char* actual, const char* message) 
{
    if (strcmp(expected, actual) != 0) 
    {
        fprintf(stderr, "Assertion Failed: %s (Expected: '%s', Actual: '%s')\n", message, expected, actual);
        return UNITT_E_FAIL;
    }

    return UNITT_E_OK;
}

/**
 * \brief Handle a test failure.
 * 
 * This function is called when a test fails. It outputs the name of the failed test.
 * 
 * \param[in] name: The name of the failed test.
 * \return Returns UNITT_E_OK.
 */
int unitt_fail_handle(const char* name) 
{
    printf("Failure in test: %s\n", name);
    return UNITT_E_OK;
}

/**
 * \brief Execute a test suite.
 * 
 * This function runs all the test cases within the provided test suites.
 * It measures the execution time for each test and outputs a summary report.
 * 
 * \param[in] suites: Array of test suites to execute.
 * \param[in] count: Number of test suites in the array.
 * \param[in] failcb: Callback function to handle test failures.
 * \param[in] specific: Specific test name to execute (NULL for all).
 * \param[in] filename: Output filename for results (NULL for stdout).
 * \return none.
 */
void unitt_execute(UNITT* suites, uint32_t count, unitt_failcb_t failcb, const char* specific, const char* filename) 
{
    UNITT* suite = NULL;
    UNITT_TCASE* tcase = NULL;
    uint64_t cstart = 0, cend = 0; // Record the start and end time of the test

    FILE* output = stdout; // Default output to standard output
    if (filename != NULL) 
    {
        output = fopen(filename, "w");
        if (output == NULL) 
        {
            perror("Failed to open output file");
            return;
        }
    }

    // Iterate over each test suite
    for (int i = 0; i < count; i++) 
    {
        suite = &suites[i];

        fprintf(output, "Running test suite: %s\n", suite->name);

        // Iterate over each test case
        for (int j = 0; j < suite->count; j++) 
        {
            tcase = &suite->tests[j];

            if (specific && strcmp(tcase->name, specific) != 0) 
            {
                continue; // Skip non-matching tests
            }

            // Execute setup function before the test
            if (tcase->setup) tcase->setup();

            // Record start time
            if (suite->clock) cstart = suite->clock();

            // Execute test function
            if (UNITT_E_OK == tcase->func())
            {
                tcase->passed++; 
            }
            else
            {
                if (failcb) failcb(tcase->name);
            }

            // Record end time
            if (suite->clock) cend = suite->clock();

            // Accumulate the time taken for the test
            if (suite->clock) tcase->time += (cend - cstart);

            // Execute teardown function after the test
            if (tcase->teardown) tcase->teardown(); 

            tcase->tested++;
        }
    }

    // Output summary of the tests
    for (int i = 0; i < count; i++) 
    {
        suite = &suites[i];

        fprintf(output, "Test suite `%s` report: \r\n", suite->name);

        // Iterate over each test case
        for (int j = 0; j < suite->count; j++) 
        {
            tcase = &suite->tests[j];

            fprintf(output, "[%s] Summary: tested %u, passed %u, time %llu us.\r\n", 
                tcase->name, 
                tcase->tested,
                tcase->passed,
                tcase->time
            );
        }
    }

    if (filename != NULL) 
    {
        fclose(output);
    }
}